import 'package:flutter/cupertino.dart';

typedef Widget SliverListViewRenderItemBuilderDelegate(BuildContext context,dynamic item,int index);
typedef Future<void> EndReachedCallback();
typedef void EndReachedStateChange(EndReachedState state);
typedef Widget SliverListViewFooterBuilder(BuildContext context, EndReachedState state);




class SliverListView extends StatelessWidget {
  /// 渲染列表项
  final SliverListViewRenderItemBuilderDelegate renderItem;
   /// 渲染下拉刷新器 可选
  final RefreshControlIndicatorBuilder renderRefresh;
  /// 列表数据
  final List<dynamic> data;
  /// 触发刷新事件 异步 可选
  final RefreshCallback onRefresh;
  /// 触发refresh零界点
  final double refreshIndicatorExtent;
  /// 触发refresh指示器的零界点
  final double refreshTriggerPullDistance;
  /// 触发上拉加载事件
  final EndReachedCallback onEndReached;
  // 触发endReached的零界点
  final double endReachedTriggerDistance;
  /// 列表尾部 可以渲染上拉加载组件
  final Widget footer;
  /// 状态变更
  final EndReachedStateChange onEndReachedStateChange;

  static const double _defaultRefreshTriggerPullDistance = 100.0;
  static const double _defaultRefreshIndicatorExtent = 60.0;

  const SliverListView({ 
    Key key, 
    @required this.renderItem,
    @required this.data, 
    this.onRefresh,
    this.renderRefresh = CupertinoSliverRefreshControl.buildSimpleRefreshIndicator,
    this.refreshIndicatorExtent = _defaultRefreshIndicatorExtent,
    this.refreshTriggerPullDistance = _defaultRefreshTriggerPullDistance,
    this.footer, 
    this.onEndReached, 
    this.endReachedTriggerDistance = _defaultEndReachedTriggerDistance,
    this.onEndReachedStateChange,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: EndReachedNotificationListenerBuilder (
        onEndReached: onEndReached,
        onEndReachedStateChange: onEndReachedStateChange,
        child: CustomScrollView(
          physics: BouncingScrollPhysics(),
          slivers: <Widget>[
            onRefresh !=null ? CupertinoSliverRefreshControl(
              builder: renderRefresh,
              refreshIndicatorExtent: refreshIndicatorExtent,
              refreshTriggerPullDistance: refreshTriggerPullDistance,
              onRefresh: onRefresh,
            ) : null,
            SliverSafeArea(
              top: false, // Top safe area is consumed by the navigation bar.
              sliver: SliverList(
                delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) => renderItem(context, data.elementAt(index), index), 
                  childCount: data.length
                ),
              ),
            ),
            footer
          ].where((w) => w !=null).toList(),
        ),
      )
    );
  }
}

class SliverListViewFooter extends StatelessWidget {
  final SliverListViewFooterBuilder builder;
  final double height;
  final Alignment alignment;
  final EndReachedState state;
  static const double _defaultHeight = 40;
  static const Alignment _defaultAlignment = Alignment.center;
  const SliverListViewFooter({Key key, @required this.builder, this.alignment = _defaultAlignment, this.height = _defaultHeight, this.state}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SliverToBoxAdapter(
      child: Container(
        height: height,
        alignment: alignment,
        child: builder(context, state),
      ),
    );
  }
}


const double _defaultEndReachedTriggerDistance = 10;

class EndReachedNotificationListenerBuilder extends StatefulWidget {
  final double endReachedTriggerDistance;
  final EndReachedCallback onEndReached;
  final Widget child;
  final EndReachedStateChange onEndReachedStateChange;

  static EndReachedState of(BuildContext context) {
    final _EndReachedNotificationListenerBuilderState builderState = context.rootAncestorStateOfType(const TypeMatcher<_EndReachedNotificationListenerBuilderState>());
    return builderState.state;
  }

  EndReachedNotificationListenerBuilder({
    Key key, 
    this.endReachedTriggerDistance = _defaultEndReachedTriggerDistance, 
    this.onEndReached, 
    this.child,
    this.onEndReachedStateChange,
  }) : super(key: key);

  _EndReachedNotificationListenerBuilderState createState() => _EndReachedNotificationListenerBuilderState();
}

enum EndReachedState {
  /// 未激活
  inactive,
  /// 激活中
  active,
  // 正在加载更多
  reached, 
}


class _EndReachedNotificationListenerBuilderState extends State<EndReachedNotificationListenerBuilder> {
  EndReachedState state = EndReachedState.inactive;

  void dispatchNotification(EndReachedState newState, BuildContext context) {
    if(newState != state) {
      this.setState((){
        state = newState;
      });
      if(widget.onEndReachedStateChange != null) {
        widget.onEndReachedStateChange(newState);
      }
    }
  }

  EndReachedState transitionState(ScrollMetrics metrics, bool scrolling) {
    switch (state) {
      case EndReachedState.inactive:
        if(metrics.maxScrollExtent - widget.endReachedTriggerDistance<= metrics.pixels) {
          return EndReachedState.active;
        }
        break;
      case EndReachedState.active:
        if(!scrolling) {
          return EndReachedState.reached;
        }
        break;
      case EndReachedState.reached:
        widget.onEndReached()..whenComplete((){
          dispatchNotification(EndReachedState.inactive, context);
        });
        break;
      default:
        break;
    }
    return state;
  }

  @override
  Widget build(BuildContext context) {
    if(widget.onEndReached == null) {
      return widget.child;
    }
    return NotificationListener<ScrollEndNotification>(
      child: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification scrollNotification) {
          ScrollMetrics metrics = scrollNotification.metrics;
          bool over = metrics.viewportDimension<metrics.maxScrollExtent;
          if(over) {
            EndReachedState newState = transitionState(metrics, true);
            dispatchNotification(newState, context);
          }
          return false;
        },
        child: widget.child
      ),
      onNotification: (ScrollEndNotification scrollNotification) {
        ScrollMetrics metrics = scrollNotification.metrics;
        bool over = metrics.viewportDimension<metrics.maxScrollExtent;
        if(over) {
          EndReachedState newState = transitionState(metrics, false);
          dispatchNotification(newState, context);
        }
        return false;
      },
    );
  }
}

